package ferp.core.ai.tree2;

import ferp.core.card.Card;
import ferp.core.game.Game;
import ferp.core.player.Hand;

/**
* User: igorgok
* Date: 3/12/12 1:47 PM
*/
final class Node extends LinkedList.Entry<Node>
{
  // node pool
  public static final Pool<Node> pool = new Pool<Node>()
  {
    @Override
    protected LinkedList<Node> initialize() {return new LinkedList<Node>(Node.class);}
    @Override
    protected Node create() {return new Node();}
  };

  // identical nodes set
  public static final HashSet set = new HashSet();

  // 16 bit value representing position flags and score: flags:4 score2:4 score1:4 score0:4
  //public short score;
  public short propagated;
  // winner
  public byte winner;
  // number of references from parent upper level nodes
  public short parents;
  // next player
  public byte player;
  // hands
  public final int[] hands = new int[Game.PLAYERS];

  // duplicate hashset's bucket index
  public int bucketIndex = HashSet.NO_BUCKET;

  // next and previous nodes in hashset bucket
  public Node nextInHashset;
  public Node previousInHashset;

  // child positions at round + 1
  public final LinkedList<Move> children = new LinkedList<Move>(Move.class);

  // new root node
  public static Node create(Level level, byte starter, int[] hands)
  {
    Node n = pool.get();

    //n.score = 0;
    n.propagated = 0;
    // root has no parent
    n.parents = 0;
    // first player
    n.player = starter;
    // populate hands
    n.hands[0] = hands[0];
    n.hands[1] = hands[1];
    n.hands[2] = hands[2];

    // initialize level 0
    level.pending.addLast(n);

    return n;
  }

  // new child position
  public static Node create(Level level, Node parent, int[] hands, byte player, byte winner, Card c0, Card c1, Card c2)
  {
    Node n = pool.get();

    //n.score = Score.increment(parent.score, winner);
    n.propagated = 0;
    n.winner = winner;
    n.parents = 1;
    n.player = player;
    // set hands
    //System.arraycopy(hands, 0, n.hands, 0, Game.PLAYERS);
    n.hands[0] = hands[0];
    n.hands[1] = hands[1];
    n.hands[2] = hands[2];

    // add this position and a move leading to it to the parent's child list
    parent.children.addLast(Move.create(c0, c1, c2, n));

    // add this node to the level' list of newly created positions
    level.pending.addLast(n);

    return n;
  }

  // key for identical leafs
  public static Node key(byte player, int[] hands)
  {
    dummy.player = player;
    //System.arraycopy(hands, 0, dummy.hands, 0, Game.PLAYERS);
    dummy.hands[0] = hands[0];
    dummy.hands[1] = hands[1];
    dummy.hands[2] = hands[2];

    return dummy;
  }

  // IMPORTANT: as there is no parent link this method does not update any information in existing parent nodes
  public final void destroy()
  {
    // debug code
/*
    if (parents > 1)
      throw new AssertionError("parents > 1");
*/

    // traverse children and decrease the number of references from parents
    // we do not actually clear children here, see set() method
    for (children.start(); children.hasNextEntry();)
    {
      Move move = children.nextEntry();

      // destroy move object
      move.destroy();
    }

    //this.score |= Score.DESTROYED;
    //this.propagated |= Score.DESTROYED;
    this.propagated = 0;
    // clear children
    children.clear();
    // remove from leafs map
    if (bucketIndex != HashSet.NO_BUCKET)
      set.remove(this);
    // now back to the pool
    pool.put(this);
  }

  @Override
  public String toString()
  {
    StringBuilder sb = new StringBuilder(512);

    //sb.append("pa: ").append(parents).append(", sc: ").append(Score.toString(score)).append('/').append(Score.toString(propagated))
    sb.append("pa: ").append(parents).append(", sc: ").append(Score.toString(propagated))
      .append(", w: ").append(winner).append(", st: ").append(player).append(", ch: ").append(children.size())
      .append(", h0 [").append(Hand.dump(hands[0])).append(']')
      .append(", h1 [").append(Hand.dump(hands[1])).append(']')
      .append(", h2 [").append(Hand.dump(hands[2])).append(']');

    return sb.toString();
  }

  @Override
  public int hashCode()
  {
    int result = 1;

    result = (result << 5) - result + player;
    result = (result << 5) - result + hands[0];
    result = (result << 5) - result + hands[1];
    result = (result << 5) - result + hands[2];

    return result;
  }

  @Override
  public boolean equals(Object o)
  {
    Node n = (Node)o;

    return player == n.player && hands[0] == n.hands[0] && hands[1] == n.hands[1] && hands[2] == n.hands[2];
  }

  // dummy persistent key object used for searches
  private static final Node dummy = new Node();
}
